<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module">
    import {setLegacyOptimizations} from '../../lib/utils/settings.js';
    setLegacyOptimizations(Boolean(window.location.search.match('legacyOptimizations')));
  </script>
  <script type="module" src="../../polymer-legacy.js"></script>
<body>

  <dom-module id="behavior-registered">
    <template>
      <div id="content"></div>
    </template>
  </dom-module>

  <dom-module id="template-from-base">
    <template>
      <div id="from-base">should not be used</div>
    </template>
  </dom-module>

  <script type="module">
    import { Polymer, html } from '../../polymer-legacy.js';
    window.BehaviorA = {
      properties: {

        label: {
          type: String,
          observer: '_labelChanged'
        },

        hasOptionsA: {
          readOnly: true,
          notify: true
        },

        overridableProperty: {
          value: false
        },

        overridablePropertyB: {
          value: false
        },

        hasBehaviorA: {
          value: true
        },

        computeADependency: {
          value: true
        },

        computeA: {
          computed: '_computeProp(computeADependency)'
        }
      },

      observers: [],

      _simpleProperty: 'A',

      hostAttributes: {
        behavior: 'A',
        element: 'A',
        user: 'A'
      },

      listeners: {
        change: '_changeHandler'
      },

      ready: function() {
        this.__readyA = true;
      },

      _labelChanged: function(label) {
        this.__label = label;
      },

      _changeHandler: function(e) {
        this.__change = e.detail.value;
      },

      _computeProp: function(a) {
        return a;
      }
    };

    window.BehaviorB = {
      properties: {

        disabled: {
          type: Boolean,
          value: false,
          observer: '_disabledChanged'
        },

        hasOptionsB: {
          readOnly: true,
          notify: true
        },

        hasBehaviorB: {
          value: true
        },

        overridablePropertyB: {
          value: true
        },

        computeADependencyDependency: {
          value: 'hi'
        },

        computeADependency: {
          computed: '_computeProp(computeADependencyDependency)'
        }

      },

      hostAttributes: {
        behavior: 'B',
        element: 'B',
        user: 'B'
      },

      _simpleProperty: 'B',

      _disabledChanged: function(disabled) {
        this.__disabled = disabled;
      },

      ready: function() {
        this.__readyB = true;
      },
    };

    window.BehaviorC = {

      properties: {

        hasBehaviorC: {
          value: true
        }

      },

      _simpleProperty: 'C'
    };

    window.BehaviorD = {

      properties: {

        hasBehaviorD: {
          value: true
        }

      },
      _simpleProperty: 'D'

    };

    Polymer({
      is: 'single-behavior',

      behaviors: [
        window.BehaviorA
      ],

      properties: {},
      observers: [],
      hostAttributes: {},
      listeners: {}
    });

    Polymer({
      is: 'multi-behaviors',

      behaviors: [
        window.BehaviorA,
        window.BehaviorB
      ],

      hostAttributes: {
        element: 'element'
      },

      properties: {

        foo: {
          type: String,
          reflectToAttribute: true,
          readOnly: true,
          observer: '_fooChanged'
        },

        overridableProperty: {
          value: true
        }

      },

      _fooChanged: function(foo) {
        this.__foo = foo;
      },
    });

    Polymer({
      is: 'nested-behaviors',

      behaviors: [
        [window.BehaviorB, [window.BehaviorC, window.BehaviorB], window.BehaviorA],
        [window.BehaviorD]
      ]
    });

    window.registerBehavior1 ={
      beforeRegister: function() {
        this._createPropertyObserver('beforeProp', 'beforePropChanged1');
        this.beforeRegisterCount++;
        this.beforeRegisterBehaviors = this.behaviors;
      },
      registered: function() {
        this._createPropertyObserver('prop', 'propChanged1');
        this._createMethodObserver('propChanged2(prop)');
        this.registeredCount++;
        this.registeredProps = [this.prop1, this.prop2, this.prop3, this.prop4];
        this.registeredBehaviors = this.behaviors;
      },
      prop1: true,
      ready: function() {
        this._ensureAttribute('attr', true);
      },
      beforePropChanged1: function() {
        this.beforePropChangedCalled = true;
      },
      propChanged1: function() {
        this.propChanged1Called = true;
      },
      propChanged2: function() {
        this.propChanged2Called = true;
      }
    };

    window.registerBehavior2 ={
      prop2: true,
      beforeRegister: function() {
        this.beforeRegisterCount++;
      },
      registered: function() {
        this.registeredCount++;
      }
    };

    window.registerBehavior3 ={
      prop3: true,
      beforeRegister: function() {
        this.beforeRegisterCount++;
      },
      registered: function() {
        this.registeredCount++;
      }
    };

    Polymer({
      behaviors: [
        window.registerBehavior1,
        window.registerBehavior2,
        window.registerBehavior3
      ],
      prop4: true,
      beforeRegister: function() {
        this.beforeRegisterCount++;
      },
      registered: function() {
        this.registeredCount++;
      },

      beforeRegisterCount: 0,
      registeredCount: 0,

      is: 'behavior-registered'
    });

    window.templateBehavior1 = {
      _template: html`<div id="from-behavior1"></div>`
    };

    window.templateBehavior2 = {
      _template: html`<div id="from-behavior2"></div>`
    };

    window.templateBehaviorFromRegistered = {
      registered: function() {
        this._template = html`<div id="behavior-from-registered"></div>`;
      }
    };

    Polymer({
      is: 'template-from-registered',
      registered: function() {
        this._template = html`<div id="from-registered"></div>`;
      }
    });

    Polymer({
      is: 'template-from-base',
      behaviors: [
        window.templateBehavior1
      ]
    });

    Polymer({
      is: 'template-from-behavior',
      behaviors: [
        window.templateBehavior1
      ]
    });

    Polymer({
      is: 'template-from-behavior-overridden',
      behaviors: [
        window.templateBehavior1,
        window.templateBehavior2
      ]
    });

    Polymer({
      is: 'template-from-behavior-registered',
      behaviors: [
        window.templateBehaviorFromRegistered
      ]
    });

    const getTemplateFromFunction = () => html`<div id="from-function">[[prop]]</div>`;
    Polymer({
      is: 'template-from-function',
      properties: { prop: {value: 'from-function'} },
      _template: getTemplateFromFunction
    });

    const getTemplateFromBehaviorFunction = () => html`<div id="from-behavior-function">[[prop]]</div>`;
    const functionTemplateBehavior = {
      _template: getTemplateFromBehaviorFunction
    };
    Polymer({
      is: 'template-from-behavior-function',
      properties: { prop: {value: 'from-behavior-function'} },
      behaviors: [functionTemplateBehavior]
    });

    window.ModifyObserversBehavior = {

      __barChangedCalled: 0,

      beforeRegister: function() {
        const observers = this.constructor.generatedFrom.observers;
        this.constructor.generatedFrom.observers = observers.concat([
          '_barChanged(bar)'
        ]);
      },

      _barChanged: function() {
        this.__barChangedCalled++;
      }

    };

    Polymer({
      is: 'modify-observers-via-behavior',
      __zonkChangedCalled: 0,
      observers: [
        '_zonkChanged(zonk)'
      ],
      behaviors: [
        window.ModifyObserversBehavior
      ],
      _zonkChanged: function() {
        this.__zonkChangedCalled++;
      }
    });

    Polymer({
      is: 'behavior-properties',
      behaviors: [window.BehaviorA]
    });

    Polymer({
      is: 'no-accessors-behavior',
      behaviors: [{
        _noAccessors: true,
        properties: {
          nug: String
        },
        foo: function() {},
        bar: true
      }],
      _noAccessors: true,
      zot: 'zot'
    });

    Polymer({
      is: 'override-default-value',
      behaviors: [
        {
          properties: {
            foo: { value: 'a' },
            bar: { value: 'a' },
            ziz: { value: 'a' }
          }
        },
        {
          properties: {
            foo: { value: 'b' },
            bar: String,
            zot: { value: 'b' },
            ziz: { value: 'b' }
          }
        },

      ],

      properties: {
        foo: String,
        zot: String,
        ziz: { value: 'c' }
      }
    });

    Polymer({
      is: 'property-observer-readonly',
      behaviors: [
        {
          observers: ['_changed(bar)'],
          _changed() {}
        }
      ],

      properties: {
        bar: {readOnly: true}
      }
    });

</script>

  <test-fixture id="single">
    <template>
      <single-behavior></single-behavior>
    </template>
  </test-fixture>

  <test-fixture id="multi">
    <template>
      <multi-behaviors user="user"></multi-behaviors>
    </template>
  </test-fixture>

  <test-fixture id="nested">
    <template>
      <nested-behaviors></nested-behaviors>
    </template>
  </test-fixture>

  <test-fixture id="registered">
    <template>
      <behavior-registered></behavior-registered>
    </template>
  </test-fixture>

  <test-fixture id="from-registered">
    <template>
      <template-from-registered></template-from-registered>
    </template>
  </test-fixture>

  <test-fixture id="from-base">
    <template>
      <template-from-base></template-from-base>
    </template>
  </test-fixture>

  <test-fixture id="from-behavior">
    <template>
      <template-from-behavior></template-from-behavior>
    </template>
  </test-fixture>

  <test-fixture id="from-behavior-overridden">
    <template>
      <template-from-behavior-overridden></template-from-behavior-overridden>
    </template>
  </test-fixture>

  <test-fixture id="from-behavior-registered">
    <template>
      <template-from-behavior-registered></template-from-behavior-registered>
    </template>
  </test-fixture>

  <test-fixture id="from-function">
    <template>
      <template-from-function></template-from-function>
    </template>
  </test-fixture>

  <test-fixture id="from-behavior-function">
    <template>
      <template-from-behavior-function></template-from-behavior-function>
    </template>
  </test-fixture>


  <test-fixture id="modify-observers-via-behavior">
    <template>
      <modify-observers-via-behavior></modify-observers-via-behavior>
    </template>
  </test-fixture>

  <test-fixture id="behavior-properties">
    <template>
      <behavior-properties></behavior-properties>
    </template>
  </test-fixture>

  <test-fixture id="no-accessors-behavior">
    <template>
      <no-accessors-behavior></no-accessors-behavior>
    </template>
  </test-fixture>

  <test-fixture id="override-default-value">
    <template>
      <override-default-value></override-default-value>
    </template>
  </test-fixture>

  <test-fixture id="property-observer-readonly">
    <template>
      <property-observer-readonly></property-observer-readonly>
    </template>
  </test-fixture>

<script type="module">
import { Polymer } from '../../polymer-legacy.js';

suite('single behavior element', function() {
  var el;

  setup(function() {
    el = fixture('single');
  });

  test('is on prototype', function() {
    assert.equal(el.is, 'single-behavior');
  });

  test('instance behaviors, properties, observers, hostAttributes, listeners', function() {
    assert.isOk(el.behaviors);
    assert.isOk(el.properties);
    assert.isOk(el.observers);
    assert.isOk(el.hostAttributes);
    assert.isOk(el.listeners);
  });

  test('ready from behavior', function() {
    assert.equal(el.__readyA, true);
  });

  test('properties from behavior', function() {
    el.label = 'foo';
    assert.equal(el.__label, 'foo');
  });

  test('listener from behavior', function() {
    el.fire('change', {value: 'bar'});
    assert.equal(el.__change, 'bar');
  });

  test('property info from behavior', function() {
    assert.equal(el._hasNotifyEffect('hasOptionsA'), true);
    assert.equal(el._hasReadOnlyEffect('hasOptionsA'), true);
    assert.equal(typeof el._setHasOptionsA, 'function');
  });

  test('compute property from behavior', function() {
    assert.equal(el.computeA, true);
  });

  test('special properties not copied from behavior to element', function() {
    const el = fixture('behavior-properties');
    assert.notOk(el.properties);
    assert.notOk(el.observers);
    assert.notOk(el.hostAttributes);
    assert.notOk(el.listeners);
  });

  test('properties on objects marked with `_noAccessors` are copied to class', function() {
    const el = fixture('no-accessors-behavior');
    assert.ok(el.foo);
    assert.isTrue(el.bar);
    assert.equal(el.zot, 'zot');
    el.setAttribute('nug', 'nug');
    assert.equal(el.nug, 'nug');
  });

  test('behavior default values can be overridden', function() {
    const el = fixture('override-default-value');
    assert.equal(el.foo, 'b');
    assert.equal(el.bar, 'a');
    assert.equal(el.zot, 'b');
    assert.equal(el.ziz, 'c');
  });

  test('readOnly not applied when property was previously observed', function() {
    const el = fixture('property-observer-readonly');
    el.bar = 5;
    assert.equal(el.bar, 5);
  });

});

suite('behavior.registered', function() {
  test('can install dynamic properties', function() {
    var el = fixture('registered');
    assert.ok(el.$.content);
    el.prop = 42;
    assert.isTrue(el.propChanged1Called);
    assert.isTrue(el.propChanged2Called);
    assert.isTrue(el.hasAttribute('attr'));
  });

  test('called once for each behavior with access to element prototype', function() {
    var el = fixture('registered');
    assert.equal(el.registeredCount, 4);
    assert.equal(el.registeredBehaviors.length, 3);
    assert.equal(el.registeredBehaviors, el.behaviors);
    assert.deepEqual(el.registeredProps, [true, true, true, true]);
  });

});

suite('behavior.beforeRegister', function() {
  test('can install dynamic properties', function() {
    var el = fixture('registered');
    assert.ok(el.$.content);
    el.beforeProp = 43;
    assert.isTrue(el.beforePropChangedCalled);
  });

  test('called once for each behavior with access to element prototype', function() {
    var el = fixture('registered');
    assert.equal(el.beforeRegisterCount, 4);
    assert.equal(el.beforeRegisterBehaviors.length, 3);
    assert.equal(el.beforeRegisterBehaviors, el.behaviors);
  });

  test('modify element observers', function() {
    var el = fixture('modify-observers-via-behavior');
    el.bar = 1;
    assert.equal(el.__barChangedCalled, 1);
    el.zonk = 1;
    assert.equal(el.__zonkChangedCalled, 1);
  });

});

suite('multi-behaviors element', function() {
  var el;

  setup(function() {
    el = fixture('multi');
  });

  test('ready from behaviors', function() {
    assert.equal(el.__readyA, true);
    assert.equal(el.__readyB, true);
  });

  test('properties from behaviors', function() {
    el.label = 'foo';
    assert.equal(el.__label, 'foo');
    el.disabled = true;
    assert.equal(el.__disabled, true);
  });

  test('properties from itself', function() {
    assert.isDefined(el._setFoo, 'readOnly setter not available');
    el._setFoo('bar');
    assert.equal(el.__foo, 'bar', 'observer not getting called');
    assert.equal(el.getAttribute('foo'), 'bar', 'not getting reflected');
  });

  test('listener from behaviors', function() {
    el.fire('change', {value: 'bar'});
    assert.equal(el.__change, 'bar');
  });

  test('property info from behavior A', function() {
    assert.equal(el._hasNotifyEffect('hasOptionsA'), true);
    assert.equal(el._hasReadOnlyEffect('hasOptionsA'), true);
    assert.equal(typeof el._setHasOptionsA, 'function');
  });

  test('property info from behavior B', function() {
    assert.equal(el._hasReadOnlyEffect('hasOptionsB'), true);
    assert.equal(el._hasNotifyEffect('hasOptionsB'), true);
    assert.equal(typeof el._setHasOptionsB, 'function');
  });

  test('computed property dependency can become a computed property', function() {
    assert.equal(el.computeA, 'hi');
  });

  test('multi-behavior overrides ordering', function() {
    assert(el.overridableProperty, 'Behavior property was not overridden by prototype');
    assert(el.overridablePropertyB, 'Behavior config-property was not overridden by sub-behavior');
  });

  test('hostAttributes ordering', function() {
    assert.equal(el.attributes.behavior.value, 'B', 'Behavior hostAttribute not overridden by younger behavior');
    assert.equal(el.attributes.element.value, 'element', 'Behavior hostAttribute overridden by behavior');
    assert.equal(el.attributes.user.value, 'user', 'Behavior hostAttribute overrode user attribute');
  });

  test('behavior is null generates warning', function() {
    sinon.spy(console, 'warn');
    Polymer({
      is: 'behavior-null',
      behaviors: [
        null
      ]
    });
    document.createElement('behavior-null');
    assert.equal(console.warn.callCount, 1, 'Null behaviour should generate warning');
    console.warn.restore();
  });

  test('behavior array is unique', function() {
    Polymer({
      is: 'behavior-unique',
      behaviors: [window.BehaviorA, window.BehaviorA]
    });
    assert.equal(document.createElement('behavior-unique').behaviors.length, 1);
  });

  test('duplicate behaviors keep first behavior', function() {
    Polymer({
      is: 'behavior-unique-last-behavior',
      behaviors: [window.BehaviorA, window.BehaviorB, window.BehaviorC, window.BehaviorA, window.BehaviorB]
    });
    var behaviors = document.createElement('behavior-unique-last-behavior').behaviors;
    assert.deepEqual(behaviors, [window.BehaviorC, window.BehaviorA, window.BehaviorB]);
  });

});

suite('nested-behaviors element', function() {
  var el;

  setup(function() {
    el = fixture('nested');
  });

  test('nested-behavior dedups', function() {
    assert.equal(el.behaviors.length, 4);
  });

  test('nested-behavior overrides ordering', function() {
    assert.ok(el.hasBehaviorA, "missing BehaviorA");
    assert.ok(el.hasBehaviorB, "missing BehaviorB");
    assert.ok(el.hasBehaviorC, "missing BehaviorC");
    assert.ok(el.hasBehaviorD, "missing BehaviorD");
    assert.equal(el._simpleProperty, 'D', 'Behavior simple property was not overridden by sub-behavior');
  });

});

suite('templates from behaviors', function() {

  test('template from registered callback', function() {
    var el = fixture('from-registered');
    assert.ok(el.shadowRoot.querySelector('#from-registered'));
  });

  test('template from base', function() {
    var el = fixture('from-base');
    assert.notOk(el.shadowRoot.querySelector('#from-base'));
    assert.ok(el.shadowRoot.querySelector('#from-behavior1'));
  });

  test('template from behavior', function() {
    var el = fixture('from-behavior');
    assert.ok(el.shadowRoot.querySelector('#from-behavior1'));
  });

  test('template from overriding behavior', function() {
    var el = fixture('from-behavior-overridden');
    assert.notOk(el.shadowRoot.querySelector('#from-behavior1'));
    assert.ok(el.shadowRoot.querySelector('#from-behavior2'));
  });

  test('template from behavior registered callback', function() {
    var el = fixture('from-behavior-registered');
    assert.ok(el.shadowRoot.querySelector('#behavior-from-registered'));
  });

});

suite('template from function', function() {
  
  test('template function assigned to _template', () => {
    var el = fixture('from-function');
    const div = el.shadowRoot.querySelector('#from-function');
    assert.ok(div);
    assert.equal(div.textContent, 'from-function');
  });

  test('template function assigned to _template in behavior', () => {
    var el = fixture('from-behavior-function');
    const div = el.shadowRoot.querySelector('#from-behavior-function');
    assert.ok(div);
    assert.equal(div.textContent, 'from-behavior-function');
  });
  
});

</script>

</body>
</html>
